/**
  ******************************************************************************
  * @file 
  * @brief 
  * @author lixin  
	          2019/01/29 zsy modified
  * @version V1.0.0
  * @date 2019/01/29
  ******************************************************************************
  */
	
/* Includes ------------------------------------------------------------------*/
#include "w5500_socket.h"
#include "w5500_fun.h"
#include "A_shell.h"
/* Private defines -----------------------------------------------------------*/
#define SOCK_ANY_PORT_NUM  0xC000;

#define CHECK_SOCKNUM()   \
   do{                    \
      if(sn > 8) return SOCKERR_SOCKNUM;   \
   }while(0);             \

#define CHECK_SOCKMODE(mode)  \
   do{                     \
      if((getSn_MR(sn) & 0x0F) != mode) return SOCKERR_SOCKMODE;  \
   }while(0);              \

#define CHECK_SOCKINIT()   \
   do{                     \
      if((getSn_SR(sn) != SOCK_INIT)) return SOCKERR_SOCKINIT; \
   }while(0);              \
	
#define CHECK_SOCKDATA()   \
   do{                     \
      if(len == 0) return SOCKERR_DATALEN;   \
   }while(0); 
/* Private Struct  -----------------------------------------------------------*/

/* Private enum    -----------------------------------------------------------*/


/* Private Variable  ---------------------------------------------------------*/
//Pointer

//Array

//Const
static uint16_t sock_any_port = SOCK_ANY_PORT_NUM;

static uint16_t sock_is_sending = 0;

/* Private function prototypes -----------------------------------------------*/


/***********************************************************************************
 * @brief  Initialize the socket configuration information function
 * @param  s        : Specifies the socket number to use.
 * @param  protocol : Specify socket communication protocol.
 * @retval Return whether to configure success information.
 **********************************************************************************/ 
int8_t socket(SOCKET sn, uint8_t protocol, uint16_t port, uint8_t flag)
{

	CHECK_SOCKNUM();		
	if (((protocol & 0x0F) != Sn_MR_TCP)    &&\
			((protocol & 0x0F) != Sn_MR_UDP)    &&\
			((protocol & 0x0F) != Sn_MR_IPRAW)  &&\
			((protocol & 0x0F) != Sn_MR_MACRAW) &&\
			((protocol & 0x0F) != Sn_MR_PPPOE))
		return SOCKERR_SOCKMODE;
	
	if((flag & 0x06) != 0) return SOCKERR_SOCKFLAG;

	if(flag != 0)
	{
   	switch(protocol)
   	{
   	   case Sn_MR_TCP:
   	      if((flag & (SF_TCP_NODELAY|SF_IO_NONBLOCK))==0) return SOCKERR_SOCKFLAG;
   	      break;
   	   case Sn_MR_UDP:
   	      if(flag & SF_IGMP_VER2)
   	      {
   	         if((flag & SF_MULTI_ENABLE)==0) return SOCKERR_SOCKFLAG;
   	      }
   	      #if _WIZCHIP_ == 5500
      	      if(flag & SF_UNI_BLOCK)
      	      {
      	         if((flag & SF_MULTI_ENABLE) == 0) return SOCKERR_SOCKFLAG;
      	      }
   	      #endif
   	      break;
   	   default:
   	      break;
   	}
   }
	close(sn);
    Ashell_print("close socket!!\r\n");
	setSn_MR(sn, protocol | flag);//WIZCHIP_WRITE(Sn_MR(sn) ,protocol | flag);
	setSn_KPALVTR(sn, 0x02); 
	if (!port) 
	{
		port = sock_any_port++;
		if(sock_any_port == 0xFFF0) sock_any_port = SOCK_ANY_PORT_NUM;		
	}
	setSn_PORT(sn, port); 
	setSn_CR(sn,Sn_CR_OPEN);// run sockinit Sn_CR
	/* wait to process the command... */
	while(getSn_CR(sn));//WIZCHIP_READ(Sn_CR(sn)) );
	/* ------- */

//	 sock_io_mode |= ((flag & SF_IO_NONBLOCK) << sn);   
//   sock_is_sending &= ~(1<<sn);
//   sock_remained_size[sn] = 0;
//   sock_pack_info[sn] = 0;
	while(getSn_SR(sn) == SOCK_CLOSED);	
	//Ashell_print("open socket success\r\n");
	return sn;
}

/***********************************************************************************
 * @brief close specified socket sn
 * ex:
 * @par 
 * None
 * @retval None
 **********************************************************************************/  
int8_t close(SOCKET sn)
{
	setSn_CR(sn,Sn_CR_CLOSE);
	/* wait to process the command... */
	while( getSn_CR(sn) );
	/* clear all interrupt of the socket. */
	setSn_IR(sn, 0xFF);
//	sock_is_sending &= ~(1<<sn);
//	sock_remained_size[sn] = 0;
//	sock_pack_info[sn] = 0;
	while(getSn_SR(sn) != SOCK_CLOSED);
	return SOCK_OK;		
}

/***********************************************************************************
 * @brief This function established  the connection for the channel in 
          passive (server) mode. This function waits for the request from the peer.
 * ex:
 * @par 
 * None
 * @retval 1 for success else 0.
 **********************************************************************************/ 
int8_t listen(SOCKET sn)
{
	CHECK_SOCKNUM();
	CHECK_SOCKMODE(Sn_MR_TCP);
	CHECK_SOCKINIT();	

	if (getSn_SR(sn) != SOCK_INIT)	return SOCKERR_SOCKINIT;	// 判断Sn_SR是否处于初始化状态
	
	setSn_CR(sn,Sn_CR_LISTEN);		 // MCU配置W5500为监听状态	
	
	while(getSn_CR(sn));           // 配置完成，Sn_CR自动清零

	while(getSn_SR(sn) != SOCK_LISTEN)
	{
		if(getSn_CR(sn) == SOCK_CLOSED)
		{
			close(sn);
			return SOCKERR_SOCKCLOSED;
		}
	}
	return SOCK_OK;	
}

/**
* @brief  Issue connection instruction function
* @note   This function is used by the client(W5500 bord as client) to send a connection instruction to the server(PC as server).
* @param  sn  : Specifies the socket number to use.
* @param  addr: Specifies the IP address of  Request to create .
* @param  port: Specifies the port of  Request to create .
* @retval Return timeout message or connect success message.
*/
int8_t connect(uint8_t sn, uint8_t * addr, uint16_t port)
{
	CHECK_SOCKNUM();
	CHECK_SOCKMODE(Sn_MR_TCP);
	CHECK_SOCKINIT();
//	M20140501 : For avoiding fatal error on memory align mismatched
//	if( *((uint32_t*)addr) == 0xFFFFFFFF || *((uint32_t*)addr) == 0) return SOCKERR_IPINVALID;
	{
		uint32_t taddr;
		taddr = ((uint32_t)addr[0] & 0x000000FF);
		taddr = (taddr << 8) + ((uint32_t)addr[1] & 0x000000FF);
		taddr = (taddr << 8) + ((uint32_t)addr[2] & 0x000000FF);
		taddr = (taddr << 8) + ((uint32_t)addr[3] & 0x000000FF);
		if( taddr == 0xFFFFFFFF || taddr == 0) return SOCKERR_IPINVALID;
	}
	if(port == 0) return SOCKERR_PORTZERO;
	setSn_DIPR(sn,addr);
	setSn_DPORT(sn,port);
	
	setSn_CR(sn,Sn_CR_CONNECT);
	while(getSn_CR(sn));
	//   if(sock_io_mode & (1<<sn)) return SOCK_BUSY;
	while(getSn_SR(sn) != SOCK_ESTABLISHED)
	{   
		if (getSn_IR(sn) & Sn_IR_TIMEOUT)
		{
			setSn_IR(sn, Sn_IR_TIMEOUT);
			return SOCKERR_TIMEOUT;
		}
	}
	return SOCK_OK;
}


/**
* @brief  Issue disconnect command
* @note   This function is used by the client(W5500 bord as client) to send a connection disconnect to the server(PC as server).
* @param  sn  : Specifies the socket number to use.
* @retval Return timeout message or disconnect success message.
*/
int8_t disconnect(uint8_t sn)
{
	CHECK_SOCKNUM();
	CHECK_SOCKMODE(Sn_MR_TCP);
	setSn_CR(sn,Sn_CR_DISCON);
	/* wait to process the command... */
	while(getSn_CR(sn));
	sock_is_sending &= ~(1<<sn);
	//   if(sock_io_mode & (1<<sn)) return SOCK_BUSY;
	while(getSn_SR(sn) != SOCK_CLOSED)
	{
		if(getSn_IR(sn) & Sn_IR_TIMEOUT)
		{
			close(sn);
			return SOCKERR_TIMEOUT;
		}
	}
	return SOCK_OK;
}




//	 /**
//  * @brief  This function used to send the data in TCP_Client mode.
//  * @note   This function is used to send data in the TCP protocol.
//  * @param  sn : Specifies the socket number to use.
//  * @param  buf: Specify the buffer to send.
//  * @param  len: Specifies the length of the data to send.
//  * @retval Return error message or length of data sent
//  */
//int32_t send(uint8_t sn, uint8_t * buf, uint16_t len)
//{
//   uint8_t tmp=0;
//   uint16_t freesize=0;
//   
//   CHECK_SOCKNUM();
//   CHECK_SOCKMODE(Sn_MR_TCP);
//   CHECK_SOCKDATA();
//   tmp = getSn_SR(sn);
//   if(tmp != SOCK_ESTABLISHED && tmp != SOCK_CLOSE_WAIT) return SOCKERR_SOCKSTATUS;
//   if( sock_is_sending & (1<<sn) )
//   {
//      tmp = getSn_IR(sn);
//			//myprintf("Sn_IR_status = %x",tmp);
//      if(tmp & Sn_IR_SEND_OK)
//      {
//         setSn_IR(sn, Sn_IR_SEND_OK);
//         sock_is_sending &= ~(1<<sn);         
//      }
//      else if(tmp & Sn_IR_TIMEOUT)
//      {
//         close(sn);
//         return SOCKERR_TIMEOUT;
//      }
//      else return SOCK_BUSY;
//   }
//   freesize = TX_RX_MAX_BUF_SIZE;
//   if (len > freesize) len = freesize; // check size not to exceed MAX size.
//   while(1)
//   {
//      freesize = getSn_TX_FSR(sn);
//      tmp = getSn_SR(sn);
//      if ((tmp != SOCK_ESTABLISHED) && (tmp != SOCK_CLOSE_WAIT))
//      {
//         close(sn);
//         return SOCKERR_SOCKSTATUS;
//      }
////      if( (sock_io_mode & (1<<sn)) && (len > freesize) ) return SOCK_BUSY;
//			if(len > freesize)  return SOCK_BUSY;
//      if(len <= freesize) break;
//   }
//   wiz_send_data(sn, buf, len);
//   setSn_CR(sn,Sn_CR_SEND);
//   /* wait to process the command... */
//   while(getSn_CR(sn));
//   sock_is_sending |= (1 << sn);
//   return len;
//}

void check_and_clear_interrupt(uint8_t socket) 
{
    uint8_t ir = getSn_IR(socket);
    
    if (ir & Sn_IR_CON) {
        Ashell_print("Socket %d connection established\n", socket);
        //setSn_IR(socket, Sn_IR_CON);  // 清除连接中断标志
    }
    if (ir & Sn_IR_DISCON) {
        Ashell_print("Socket %d disconnected\n", socket);
        //setSn_IR(socket, Sn_IR_DISCON);  // 清除断开连接中断标志
    }
    if (ir & Sn_IR_TIMEOUT) {
        Ashell_print("Socket %d timeout\n", socket);
        //setSn_IR(socket, Sn_IR_TIMEOUT);  // 清除超时中断标志
    }
    if (ir & Sn_IR_SEND_OK) {
        Ashell_print("Socket %d send ok\n", socket);
        //setSn_IR(socket, Sn_IR_SEND_OK);  // 清除发送成功中断标志
    }
}


/**
* @brief  This function used to send the data in TCP_server mode.
* @note   This function is used to send data in the TCP protocol.
* @param  sn : Specifies the socket number to use.
* @param  buf: Specify the buffer to send.
* @param  len: Specifies the length of the data to send.
* @retval 1 for success else 0.
*/
int32_t send(SOCKET sn, const uint8_t * buf, uint16_t len)
{
	uint8_t status=0;
	uint16_t ret=0;
	uint16_t freesize=0;
    
    uint16_t enter = 0;
    uint16_t ptr_RD = 0;	
    uint32_t rd_addrbsb = 0;
    
	CHECK_SOCKNUM();
	CHECK_SOCKMODE(Sn_MR_TCP);
	CHECK_SOCKDATA();	

   
	if (len > getSn_TxMAX(sn)) ret = getSn_TxMAX(sn);
	else ret = len;
        
	do
	{
		freesize = getSn_TX_FSR(sn);
		status = getSn_SR(sn);//WIZCHIP_READ(Sn_SR(sn));
		check_and_clear_interrupt(sn);
		if((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT))
		{
			ret = 0;
			break;
		}
		if(ret > freesize)  
        {
            return SOCK_BUSY;
            Ashell_print("SOCK_BUSY \r\n");
        }
		if(ret <= freesize) 
            break;
	} while (freesize < ret);

    //Ashell_print("freesize:%d,max size:%d\r\n",freesize,getSn_TxMAX(sn));
	send_data_processing(sn,(uint8_t *)buf, ret);

	setSn_CR(sn, Sn_CR_SEND);        //WIZCHIP_WRITE( Sn_CR(sn) ,Sn_CR_SEND);
    
    //读取读发送地址
    ptr_RD = getSn_TX_RD(sn);
    rd_addrbsb = (uint32_t)(ptr_RD<<8) + (sn<<5) + 0x10;
    //Ashell_print("rd_addrbsb:%x\r\n",ptr_RD);
    
	while(getSn_CR(sn))  // WIZCHIP_READ(Sn_CR(sn) ) );
	{
        Ashell_print("STUCK IN getSn_CR \r\n");
    }
		
	while ( (getSn_IR(sn) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
	{
		status = getSn_SR(sn);// WIZCHIP_READ(Sn_SR(sn));
		//Ashell_print("sn:%d, 2 net status:%x \r\n",sn,status);
		if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT) )
		{
			Ashell_print("SEND_OK Problem!! status:%x\r\n",status);
            
			close(sn);
			return 0;
		}
	}
	setSn_IR(sn, Sn_IR_SEND_OK);//WIZCHIP_WRITE( Sn_IR(sn) , Sn_IR_SEND_OK);	
	return ret;
}

/**
* @brief  Data receiv function.
* @note   This function is used to receiv data in the TCP protocol.
* @param  sn : Specifies the socket number to use.
* @param  buf: Specify the buffer to receiv.
* @param  len: Specifies the length of the data to receiv.
* @retval Return error message or length of data receiv
*/
int32_t recv(uint8_t sn, uint8_t * buf, uint16_t len)
{
	uint8_t  tmp = 0;
	uint16_t recvsize = 0;
	CHECK_SOCKNUM();
	CHECK_SOCKMODE(Sn_MR_TCP);
	CHECK_SOCKDATA();

	if(getSn_RxMAX(sn) < len) len = getSn_RxMAX(sn);
	while(1)
	{
		recvsize = getSn_RX_RSR(sn);    //已接收和保存的数据大小
		tmp = getSn_SR(sn);
		if (tmp != SOCK_ESTABLISHED)
		{
			if(tmp == SOCK_CLOSE_WAIT)
			{
				if(recvsize != 0) break;
				else if(getSn_TX_FSR(sn) == getSn_TxMAX(sn))
				{
                    Ashell_print("recv Problem!!\r\n");

                    close(sn);
                    
					return SOCKERR_SOCKSTATUS;
				}
			}
			else
			{
				close(sn);
				return SOCKERR_SOCKSTATUS;
			}
		}
//      if((sock_io_mode & (1<<sn)) && (recvsize == 0)) return SOCK_BUSY;
		if(recvsize != 0) break;
	};
	if(recvsize < len) len = recvsize;
	wiz_recv_data(sn, buf, len);
	setSn_CR(sn,Sn_CR_RECV);
	while(getSn_CR(sn));
	return len;
}

/**
* @brief  Data sending function.
* @note   This function is used to send data in the UDP protocol.
* @param  s   : Specifies the socket number to use.
* @param  buf : Specify the buffer to send.
* @param  len : Specifies the length of the data to send.
* @param  addr: Specify the IP address of the destination of the received data.
* @param  port: Specify the port of the destination of the received data.
* @retval Return timeout message or length of data sent
*/
uint16_t sendto(SOCKET sn, const uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port)
{
	uint16_t ret=0;

	if (len > getSn_TxMAX(sn)) ret = getSn_TxMAX(sn); // check size not to exceed MAX size.
	else ret = len;

	if( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) || ((port == 0x00)) )//||(ret == 0) )
	{
		/* added return value */
		ret = 0;
	}
	else
	{
		setSn_DIPR(sn,addr);
		setSn_DPORT(sn,port);
		// copy data
		send_data_processing(sn, (uint8_t *)buf, ret);
		setSn_CR(sn, Sn_CR_SEND);//WIZCHIP_WRITE( Sn_CR(sn) ,Sn_CR_SEND);
		/* wait to process the command... */
		while(getSn_CR(sn));
		/* ------- */

		while( (getSn_IR(sn) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
		{
			if (getSn_IR(sn) & Sn_IR_TIMEOUT)
			{
                /* clear interrupt */
				setSn_IR(sn, Sn_IR_SEND_OK | Sn_IR_TIMEOUT);//WIZCHIP_WRITE( Sn_IR(sn) , (Sn_IR_SEND_OK | Sn_IR_TIMEOUT)); /* clear SEND_OK & TIMEOUT */			
				return 0;
			}
		}
		setSn_IR(sn, Sn_IR_SEND_OK);//WIZCHIP_WRITE( Sn_IR(sn) , Sn_IR_SEND_OK);		
	}
	return ret;

}

/**
* @brief  Data receiving function.
* @note   This function is used to received data in the UDP protocol.
* @param  s   : Specifies the socket number to use.
* @param  buf : Specify the buffer to received.
* @param  len : Specifies the length of the data to received.
* @param  addr: Specify the source IP address of the sending data.
* @param  port: Specify the source port of the sending data.
* @retval Return timeout message or length of data sent
*/
int32_t recvfrom(SOCKET sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port)
{
	uint8_t head[8];
	uint16_t data_len=0;
	uint16_t ptr=0;
	uint32_t addrbsb =0;
	if ( len > 0 )
	{
		ptr = getSn_RX_RD(sn);
		addrbsb = (uint32_t)(ptr<<8) +  (sn<<5) + 0x18;

		switch (WIZCHIP_READ(Sn_MR(sn) ) & 0x07)
		{
			case Sn_MR_UDP :
				WIZCHIP_READ_BUF(addrbsb, head, 0x08);        
				ptr += 8;
				// read peer's IP address, port number.
				addr[0]  = head[0];
				addr[1]  = head[1];
				addr[2]  = head[2];
				addr[3]  = head[3];
				*port    = head[4];
				*port    = (*port << 8) + head[5];
				data_len = head[6];
				data_len = (data_len << 8) + head[7];

				addrbsb = (uint32_t)(ptr<<8) +  (sn<<5) + 0x18;
				WIZCHIP_READ_BUF(addrbsb, buf, data_len);                
				ptr += data_len;
				setSn_RX_RD(sn,ptr);
			break;

			case Sn_MR_IPRAW :
				WIZCHIP_READ_BUF(addrbsb, head, 0x06);        
				ptr += 6;
				addr[0]  = head[0];
				addr[1]  = head[1];
				addr[2]  = head[2];
				addr[3]  = head[3];
				data_len = head[4];
				data_len = (data_len << 8) + head[5];

				addrbsb  = (uint32_t)(ptr<<8) +  (sn<<5) + 0x18;
				WIZCHIP_READ_BUF(addrbsb, buf, data_len);        
				ptr += data_len;
				setSn_RX_RD(sn,ptr);
			break;

			case Sn_MR_MACRAW :
				WIZCHIP_READ_BUF(addrbsb, head, 0x02);
				ptr+=2;
				data_len = head[0];
				data_len = (data_len<<8) + head[1] - 2;
				if(data_len > 1514)
				{
					//Ashell_print("data_len over 1514\r\n");
					while(1);
				}

				addrbsb  = (uint32_t)(ptr<<8) +  (sn<<5) + 0x18;
				WIZCHIP_READ_BUF(addrbsb, buf, data_len);
				ptr += data_len;

				setSn_RX_RD(sn,ptr);
				break;

			default :
			break;
		}
		setSn_CR(sn, Sn_CR_RECV);//WIZCHIP_WRITE( Sn_CR(sn) ,Sn_CR_RECV);

		/* wait to process the command... */
		while(getSn_CR(sn));// WIZCHIP_READ( Sn_CR(sn)) ) ;
		/* ------- */
	}
	return data_len;
}



/******************* (C) COPYRIGHT 2018 CIQTEK Samuel *****END OF FILE****/
